package littleimsclient;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.net.*;
import java.util.regex.*;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;


import java.security.MessageDigest;

public class Client {
	/* Génération d'un vecteur RAND 
	 * 
	 */
	public static byte[] RandGen() {
		byte[] rand = new byte[16];
		Random randomGenerator = new Random();
		randomGenerator.nextBytes(rand);
		return rand;
	}	
	
	/** Calcul du RESi
	 * 
	 * @param secretKey
	 * @param rand //Généré via RandGen()
	 * @param op_c
	 * @return RESi
	 * @throws InvalidKeyException
	 * @throws ArrayIndexOutOfBoundsException
	 */
	public static byte[] RESiCalc(byte[] secretKey, byte[] rand, byte[] op_c) throws InvalidKeyException, ArrayIndexOutOfBoundsException {
		try{
			byte[] RESi = Milenage.f2(secretKey, rand, op_c);
			return RESi;
		}
		finally{
		}		
	}
	
	/** Calcul de CK
	 * 
	 * @param args
	 * @throws ArrayIndexOutOfBoundsException 
	 * @throws InvalidKeyException 
	 * @throws IOException
	 */
	public static byte[] CKCalc(byte[] secretKey, byte[] rand, byte[] op_c) throws InvalidKeyException, ArrayIndexOutOfBoundsException {
		byte[] CK = Milenage.f3(secretKey,rand,op_c);
		return CK;
	}
	
	/** Calcul de IK
	 * 
	 * @param args
	 * @throws ArrayIndexOutOfBoundsException 
	 * @throws InvalidKeyException 
	 * @throws IOException
	 */
	public static byte[] IKCalc(byte[] secretKey, byte[] rand, byte[] op_c) throws InvalidKeyException, ArrayIndexOutOfBoundsException {
		byte[] IK = Milenage.f4(secretKey,rand,op_c);
		return IK;
	}
   
     /** Calcul de la clé secrète
	 * 
	 * @param args
	 */
	public static byte[] secretKeyCalc(String mdp) {
		byte[] secretKey = new byte[16];
		byte[] mdpByte = mdp.getBytes();
		for(int i=0; i<secretKey.length; i++){
			if( i<mdpByte.length )
				secretKey[i] = mdpByte[i];
			else
				secretKey[i] = 0;
		}
		return secretKey;
	}
	
	protected static byte[] computeSipAuthenticate(byte[] sqn, byte[] amf, byte[] mac, byte[] ak) throws UnsupportedEncodingException
	{
		byte[] bNonce = new byte[sqn.length + amf.length + mac.length];
		for (int i = 0; i < bNonce.length; i++)
		{
			if (i < sqn.length) {
				bNonce[i] = (byte) (sqn[i] ^ ak[i]);
			} else if (i < (sqn.length + amf.length)) {
				bNonce[i] = amf[i - sqn.length];
			} else {
				bNonce[i] = mac[i - sqn.length - amf.length];
			}
		}
		return bNonce;
	}
	
	protected static byte[] sqnCalc(byte[] autn, byte[] ak) throws UnsupportedEncodingException
	{
		byte[] sqn = new byte[6];
		for (int i = 0; i < sqn.length; i++)
		{
				sqn[i] = (byte) (autn[i] ^ ak[i]);
		}
		return sqn;
	}
	/* Algorithm :
	 * 	AKA_ONEWAY
	 * 	AKA_ONEWAY_DH
	 *  AKA_ONEWAY_PKI
	 */
	public static void main(String[] args) throws IOException {
		if (args.length != 1) {
            System.out.println("Usage: java Client AKA_ONEWAY|AKA_ONEWAY_DH|AKA_ONEWAY_PKI");

            return;
       }
		// Client Parameters
		byte[] host = new byte[] {(byte)192,(byte)168,(byte)1,(byte)2};
		int portDest = 5080;
		InetAddress address = InetAddress.getByAddress(host);
		
		// SIP Parameters
		String ipLocal = InetAddress.getLocalHost().toString();
		String name = "bob";
		String uriPub = "sip:bob@cipango.voip";
		String uriPri = "bob@cipango.voip";
		String mdp = "1234";
		String pcscfAddr = "192.168.1.2";
		String pcscfPort = Integer.toString(portDest);
		String domaine = "cipango.voip";
		String registrationTime = "3600";
		String algorithm = args[0]; 
		
		// Create socket UDP 5060
		int portSrc = 5060;
	
		DatagramSocket socket = new DatagramSocket(portSrc);
		String strPortSrc = Integer.toString(portSrc);
		
		// Create BUFFER in/out
		byte[] buf = new byte[2048];
		
		// RANDi
		byte[] RANDi = RandGen();
    
		// Calculs
		byte[] op_c = null;
		byte[] RESi = null;
		byte[] IK = null;
		byte[] CK = null;
		byte[] secretKey = secretKeyCalc(mdp);
		
		try{
			op_c = Milenage.computeOpC(secretKey,new byte[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}); 
			RESi = RESiCalc(secretKey, RANDi, op_c);
			IK = IKCalc(secretKey, RANDi, op_c);
			CK = CKCalc(secretKey, RANDi, op_c);
		}
		catch(Exception e){
			e.printStackTrace();
		}

	    
	    //Chiffrement CK
		Cipher c = null;
		String encryptedDomain = null;
		String encryptedUri = null;
		String encryptedRESi = null;
		SecretKeySpec k = new SecretKeySpec(CK, "AES");
		
		try {
			c = Cipher.getInstance("AES");
			c.init(Cipher.ENCRYPT_MODE, k);
		
			//domaine
			byte[] byteDomain = domaine.getBytes();
			byte[] byteEncryptedDomain = c.doFinal(byteDomain);
			encryptedDomain = HexString.bufferToHex(byteEncryptedDomain);
	    
			//RESi
			byte[] byteEncryptedRESi = c.doFinal(RESi);		
			encryptedRESi = HexString.bufferToHex(byteEncryptedRESi);
	    
			//uri
			String uriDomaine = "sip:"+domaine;
			byte[] byteUri = uriDomaine.getBytes();
			byte[] byteEncryptedUri = c.doFinal(byteUri);
			encryptedUri = HexString.bufferToHex(byteEncryptedUri);
	    
		} catch (NoSuchAlgorithmException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (NoSuchPaddingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//Conversion octets -> String
	    String hexRANDi = HexString.bufferToHex(RANDi);

	    //Hash + chiffrement IK
	    //Hash
	    String champsIdentiques = "CSeq: 6 REGISTER" +
	    						"Content-Length: 0" +
	    						"From: "+name+" <"+uriPub+">;tag=1007" +
	    						"Contact: "+name+" <sip:bob@192.168.1.3:"+strPortSrc+">;+sip.instance=f2c9b56b-1780-4713-82d6-d967b0a2ddb5" +
	    						"Expires: 3600" +
	    						"To: "+name+" <"+uriPub+">";
	    
	    byte[] byteChampsIdentiques = champsIdentiques.getBytes();
	    
	    byte[] byteHash = null;
	    String encryptedHash = null;
	    
	    try {
			MessageDigest sha1 = MessageDigest.getInstance("SHA1");
			byteHash = sha1.digest(byteChampsIdentiques);		
		} catch (NoSuchAlgorithmException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		//Chiffrement IK
		SecretKeySpec k2 = new SecretKeySpec(IK, "AES");
		try {
			c.init(Cipher.ENCRYPT_MODE, k2);
			byte[] byteEncryptedHash = c.doFinal(byteHash);
			encryptedHash = HexString.bufferToHex(byteEncryptedHash);
		} catch (InvalidKeyException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		// Create request
		String requete = "REGISTER sip:"+domaine+" SIP/2.0\r\n" +
					   "CSeq: 6 REGISTER\r\n" +
					   "Content-Length: 0\r\n" +
					   "From: "+name+" <"+uriPub+">;tag=1007\r\n" +
					   "Contact: \""+name+"\" <sip:bob@192.168.1.3:"+strPortSrc+">;+sip.instance=f2c9b56b-1780-4713-82d6-d967b0a2ddb5 \r\n" +
					   "Expires: 3600 \r\n" +
					   "To: "+name+" <"+uriPub+">\r\n" +
					   "Max-Forwards: 20 \r\n" +
					   "Via: SIP/2.0/UDP 192.168.1.3:"+strPortSrc+";branch=z9hG4bKfb4622b721d3e7a86c2c5027d1f487f6383732 \r\n" +
					   "User-Agent: clientIMS \r\n" +
					   "Authorization: Digest username=\""+uriPri+"\",algorithm=\""+algorithm+"\",realm=\""+encryptedDomain+"\",nonce=\""+hexRANDi+"\",response=\""+encryptedRESi+"\",uri=\""+encryptedUri+"\",hash=\""+encryptedHash+"\" \r\n" +
					   "Call-ID: ee01ae32d98703128531a04b87eba9a4@192.168.1.3 \r\n\r\n";
	
		System.out.print(requete);
		buf = requete.getBytes();
	
		// Send request
		DatagramPacket packet = new DatagramPacket(buf, buf.length, address, portDest);
		socket.send(packet);
		System.out.println("Paquet SIP envoye");
		
		// Get response
		buf = new byte[2048];
		packet = new DatagramPacket(buf, buf.length);
		System.out.println("Attente paquet SIP");
		socket.receive(packet);
		System.out.println("Paquet SIP RECU");
		
		// Display response
		String received = new String(packet.getData(), 0, packet.getLength());
		System.out.println("Paquet SIP RECU: \n" + received);
		
		// Get received AUTN
		Pattern regex = Pattern.compile("(.*?)nonce=\"(.*?)\"",Pattern.CASE_INSENSITIVE);
		Matcher match = regex.matcher(received);
		match.find();
		String receivedAutn = match.group(2);
		byte[] byteAutn = HexString.hexToBuffer(receivedAutn);
		
		//Déchiffrement CK AUTN
		String decryptedAutn = null;
		
		try {
			c.init(Cipher.DECRYPT_MODE, k);
			byte[] bytedecryptedAutn = c.doFinal(byteAutn);
			decryptedAutn = HexString.bufferToHex(bytedecryptedAutn);
		} catch (InvalidKeyException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		
		// AUTN Calcul Local
		byte[] sqn = null;
		byte[] __amf = { 0, 0};
		byte[] mac = null;
		byte[] ak = null;
		try {
			ak = Milenage.f5(secretKey, RANDi, op_c);
			sqn = sqnCalc(HexString.hexToBuffer(decryptedAutn),ak);
			mac = Milenage.f1(secretKey, RANDi, op_c, sqn, __amf);
		}
		catch(Exception e){}
		
		byte[] _sipAuthenticate = computeSipAuthenticate(sqn, __amf, mac, ak);
		String hexSipAuth = HexString.bufferToHex(_sipAuthenticate);
		
		System.out.println("Received AUTN: " + decryptedAutn);
		
		System.out.println("Calculated AUTN: " + hexSipAuth);


		// Comparaison AUTN
		if (hexSipAuth.equals(decryptedAutn)) {
			System.out.println("Authentication successful !");
		} else {
			System.out.println("Authentication failed !");
		}
		socket.close();
	}
}
